//@author A0101056Y
#include "Operations.h"

bool Operations::hasRun = false;

Operations::Operations() {
	_availableData[0].reset();
	readFromStorage();
}

Operations& Operations::get() {
	static Operations lastOperation; 

	if (hasRun == false) {

		hasRun = true;
	}

	return lastOperation;
}

Operations::~Operations() {
	hasRun = false;
}


void Operations::readFromStorage(){
	EventStorage* ptr = &EventStorage::get();
	_eventList = ptr->getEventVectorPtr();
}

//@author A0083782W
void Operations::updateRepeat() {
	for (pos_int i = 0; i < _eventList->size(); i++) {
		if ((*_eventList)[i].getRepeatRule().frequency != RepeatFrequency::NO_REPEAT) {
			(*_eventList)[i].setMissed (false);
			(*_eventList)[i].setReminded (false);
		}
	}
}

void Operations::setTab (pos_int Num) {
	_currentTab = (CurrentTabs) Num;
}

void Operations::executeInternal (const InternalCommand& aCommand) {
	InternalType operations = aCommand.getInternalType();
	CallDueEvents * const callDuePtr = &CallDueEvents::get();
	OutputDisplay * const opPtr = & OutputDisplay::get();
	HistoryRecord * const histPtr = & HistoryRecord::get();
	Hinter * const hintPtr = & Hinter::get();
	switch (operations) {
	case InternalType::GET_HINT:
		opPtr->showHint (hintPtr->generateHint(aCommand.getCurrentInput()));
		break;
	case InternalType::CALL_EVENTS:
		callDuePtr->trigger();
		opPtr->setAvailableDue (callDuePtr->notEmpty());
		break;
	case InternalType::DEQUEUE_EVENTS:
		if (callDuePtr->notEmpty() == true) {
			_dequeuedID = callDuePtr->deQueue();
			opPtr->showDue (_eventList->at(_dequeuedID));
		}
		else {
			_dequeuedID = -1;
		}
		opPtr->setAvailableDue (callDuePtr->notEmpty());
		break;
	case InternalType::MARK_AS_DONE:
		markEvent (_dequeuedID, MarkFilter::DONE);
		break;
	case InternalType::MARK_AS_MISSED:
		markEvent (_dequeuedID, MarkFilter::MISSED);
		break;
	case InternalType::MARK_AS_REMIND:
		markEvent (_dequeuedID, MarkFilter::REMINDED);
		break;
	case InternalType::START_UP:
		displayAll();
	case InternalType::PUSH_HISTORY:
		histPtr->push_back (aCommand.getCurrentInput());
		break;
	case InternalType::GET_HISTORY:
		opPtr->displayHistory (histPtr->getCurrentString());
		break;
	case InternalType::MOVE_TO_TAIL:
		histPtr->moveToTail ();
		break;
	case InternalType::MOVE_BACK:
		histPtr->moveBack();
		break;
	case InternalType::MOVE_FRONT:
		histPtr->moveFront();
		break;
	default:
		break;
	}
}

//@author A0101056Y
void Operations::executeCommand (UserCommand& aCommand) {	
	OperationType operation = aCommand.getOperationType();
	
	Event const * anEvent;
	vector<pos_int> const* IDs;
	SEARCH_STRUCT const * searchPtr;
	ActionListPtr undoPtr = &ActionList::get();
	MARK_STRUCT const * markInfo;
	OperationType const * helpType;
	pos_int undoStep = 0;

	if (aCommand.getError() == IllegalInput::NO_PROBLEM) {
		if (aCommand.getExpectation() == Expection::NORMAL) {
			switch (operation) {
			case OperationType::UNCLEAR:
				failProgram (ERR_Operations::unclear_command);
				break;

			case OperationType::ADD: 
				undoPtr->push(aCommand);
				anEvent = (Event const *)aCommand.getInput();
				addEvent(*anEvent);
				OutputDisplay::get().showAdded ((*anEvent).getDetails());
				break;

			case OperationType::DELETE_BY_NUMBER:
				IDs = (vector<pos_int> const *)aCommand.getInput();
				if (IDs->size() == 0 ) {
					failProgram (ERR_Operations::passing_empty_delete_array);
				}
				deleteByNumber(*IDs);	
				break;

			case OperationType::MODIFY:
				_myModifyInfo.reset();
				_myModifyInfo = (*(MODIFY_STRUCT*)aCommand.getInput());
				modifyEvent (_myModifyInfo);
				break;

			case OperationType::SEARCH:
				searchPtr = (SEARCH_STRUCT const *)aCommand.getInput();
				performSearch (*searchPtr);
				break;

			case OperationType::MARK:
				markInfo = (MARK_STRUCT const *)aCommand.getInput();
				if (markInfo->IDs.size() == 0 ) {
					failProgram (ERR_Operations::passing_empty_mark_array);
				}
				markEvent(*markInfo);
				break;
		
			case OperationType::UNDO:
				IDs = (vector<pos_int> *)aCommand.getInput();
				for (pos_int i = 0; i < (*IDs)[0]; i++){
					if (undoPtr->size() != 0) {
						if (undoPtr->peek_isModify()){
							executeUndo(undoPtr->pop());
						}
						executeUndo(undoPtr->pop());
						undoStep++;
					}
					else {
						break;
					}
				}
				OutputDisplay::get().showUndoneSuccess(undoStep);
				break;

			case OperationType::HELP:
				helpType = (OperationType const *)aCommand.getInput ();
				OutputDisplay::get().showUpcomingString (Help::get().generateHelp (*helpType));
				break;

			case OperationType::CONFIG:
				OutputDisplay::get().showConfig();  //ooooo
				aCommand.setExpectation (Expection::CONFIG_STEP2);//ooooo
				break;

			case OperationType::SAVEFILE:
				saveToFile();
				OutputDisplay::get().showSave();
				break;
			case OperationType::EXIT:
				saveToFile();
				OutputDisplay::get().shutDown();
				break;

			case OperationType::CLEAR:
				OutputDisplay::get().clearConsole();
				break;
			default:
				failProgram (ERR_Operations::unknown_command);
				break;
			}
		}
		else if (aCommand.getExpectation() == Expection::CONFIG_STEP2 ) {
			int *index = (int *)aCommand.getInput();
			configStep2 (*index, aCommand);
		}
		else if (aCommand.getExpectation() == Expection::CONFIG_STEP3) {
			int *index = (int *)aCommand.getInput();
			configStep3 (*index, aCommand);
			Settings::get().saveSetting();
		}
	}
	else {
		OutputDisplay::get().displayError (aCommand.getError());
	}
	displayAll();
}

//@author A0105495A
void Operations::configStep2 (int index, UserCommand& aCommand){ //ooooo
    if (index==(int)Config_Type::DATE||index==(int)Config_Type::TIME||index==(int)Config_Type::ORDER){
        aCommand.setExpectation (Expection::CONFIG_STEP3);
        _toConfig=index;
        switch(_toConfig){
            case (int)Config_Type::DATE:
                OutputDisplay::get().displayDateConfig ();
                break;
            case (int)Config_Type::TIME:
                OutputDisplay::get().displayTimeConfig ();
                break;
            case (int)Config_Type::ORDER:
                OutputDisplay::get().displayOrderConfig ();
                break;
            default:
                aCommand.setExpectation (Expection::NORMAL);
                break;
        }
    } 
    else {
        OutputDisplay::get().displayExitConfig ();
        aCommand.setExpectation (Expection::NORMAL);
    }
}

void Operations::configStep3 (int index, UserCommand& aCommand){//ooooo
    switch(_toConfig){
        case (int)Config_Type::DATE:   
            if (index>=(int)DateDisplayFormat::DDMMYYYY&&index<=(int)DateDisplayFormat::MMDDYY){
                Settings::get().setDateDisplayFormat ((DateDisplayFormat)index);
                OutputDisplay::get().displayDateConfiged ();
                OutputDisplay::get().showConfig();
                aCommand.setExpectation (Expection::CONFIG_STEP2);
            }
            else {
                OutputDisplay::get().displayIllegalConfig ();
            }
            break;
        case (int)Config_Type::TIME:
            if (index==(int)TimeDisplayFormat::AM_PM||index==(int)TimeDisplayFormat::TWENTY_FOUR){
                Settings::get().setTimeDisplayFormat ((TimeDisplayFormat)index);
                OutputDisplay::get().displayTimeConfiged ();
                OutputDisplay::get().showConfig();
                aCommand.setExpectation (Expection::CONFIG_STEP2);
            }
            else {
                OutputDisplay::get().displayIllegalConfig ();
            }
            break;
        case (int)Config_Type::ORDER:
            if (index==(int)DisplayOrder::HIGHT_TO_LOW||index==(int)DisplayOrder::LOW_TO_HIGH){
                Settings::get().setTimeListingOrder ((DisplayOrder)index);
                OutputDisplay::get().displayOrderConfiged ();
                OutputDisplay::get().showConfig();
                aCommand.setExpectation (Expection::CONFIG_STEP2);
            }
            else {
                OutputDisplay::get().displayIllegalConfig ();
            }
            break;
        default:
            aCommand.setExpectation (Expection::NORMAL);
            break;
    }
}

//@author A0083782W
void Operations::displayAll () {
	CallDueEvents * const duePtr = &CallDueEvents::get();
	for (CurrentTabs i = CurrentTabs::TODAY; (int)i <= (int)CurrentTabs::FLOATING;
		i = (CurrentTabs)((pos_int) (i) + 1)) {
		SearchEngine tempEngine;
		SEARCH_STRUCT newStruct;
		CurrentTime nowClock;
		vector<pos_int> foundIndex;
		vector<SEARCH_RELEVANCE> searchResult;
		
		switch (i) {
		case CurrentTabs::TODAY:
			newStruct.beginTime = nowClock;
			newStruct.endTime = nowClock;
			newStruct.filters.push_back (SearchFilter::NORMAL);
			break;
		case CurrentTabs::MISSED:
			newStruct.filters.push_back (SearchFilter::MISSED);
			break;
		case CurrentTabs::FAVORITE:
			newStruct.filters.push_back (SearchFilter::FAVOURITE);
			break;
		case CurrentTabs::FLOATING:
			newStruct.filters.push_back (SearchFilter::FLOATING);
			break;
		}
		for (pos_int j = 0; j < _eventList->size(); j++) {
			tempEngine.setEverything ((*_eventList)[j], newStruct,j );
			tempEngine.searchEvents();
		}
		searchResult = tempEngine.getFoundEvents ();
		for (auto j:searchResult) {
			foundIndex.push_back (j.storageIndex);
		}
		sortEvents(foundIndex);
		_availableData[(pos_int)i].setMapping (foundIndex);
		switch (i) {
		case CurrentTabs::TODAY:
			OutputDisplay::get().displayToday (foundIndex);
			(*duePtr) = foundIndex;
			break;
		case CurrentTabs::MISSED:
			OutputDisplay::get().displayMissed (foundIndex);
			break;
		case CurrentTabs::FAVORITE:
			OutputDisplay::get().displayFavourite (foundIndex);
			break;
		case CurrentTabs::FLOATING:
			OutputDisplay::get().displayFloating (foundIndex);
			break;
		}
	}
}

//@author A0101056Y
/*
Description: 
	add an event in the storage
Preconditions: UserCommand class passes an event of Event type
Postconditions: event added in the storage
*/
void Operations::addEvent(Event anEvent){
	_eventList->push_back(anEvent);
}

//@author A0083782W
void Operations::deleteByNumber(const vector<pos_int>& IDs){ 
	vector<pos_int> deletedIDs;
	vector<pos_int> notDeletedIDs;
	pos_int nDeleted = 0;
	for (auto i:IDs) {
		int storageID = _availableData[(pos_int)_currentTab].getStorageIndex (i);
		if (storageID >= 0) {
			deletedIDs.push_back (i);
			_availableData[0].deleteMapping (i);
			ActionList* undoPtr = &ActionList::get();
			undoPtr->push((*_eventList)[storageID], OperationType::DELETE_BY_NUMBER);
			_eventList->erase (_eventList->begin() + storageID);
			nDeleted++;
		}
		else {
			notDeletedIDs.push_back(i);
		}
	}
	if (nDeleted == 0) {
		deletedIDs.clear();
		deletedIDs.shrink_to_fit();
		notDeletedIDs.clear();
		notDeletedIDs.shrink_to_fit();
	}
	OutputDisplay::get().showdeleted(deletedIDs, notDeletedIDs);
}

//@author A0101056Y
void Operations::deleteByEvent (Event anEvent){
	for (pos_int i = 0; i < _eventList->size(); i++) {
		if ((*_eventList)[i] == anEvent){
			_eventList->erase (_eventList->begin() + i);
		}
	}
}

//@author A0083782W
void Operations::modifyEvent (const MODIFY_STRUCT& info){
	vector<pos_int> modifiedIDs;
	vector<pos_int> notModifiedIDs;
	WordList newAnalyser;
	Time tempTimer1, tempTimer2;
	vector<pos_int> intList;
	ActionListPtr undoPtr = &ActionList::get();
	for (auto i:_myModifyInfo.IDs) {
		int storageID = _availableData[(pos_int)_currentTab].getStorageIndex(i);
		if ( storageID == -1) {
			notModifiedIDs.push_back(i);
		}
		else {
			modifiedIDs.push_back (i);
			undoPtr->push((*_eventList)[storageID], OperationType::MODIFY);
			if (info.modifyInfo[0] == NULL_STRING) {
				//do nothing
			}
			else if (info.modifyInfo[0] == SPACE_STRING) {
				(*_eventList)[storageID].setDetails (NULL_STRING);
			}
			else {
				(*_eventList)[storageID].setDetails (info.modifyInfo[0]);
			}

			if (info.modifyInfo[1] == NULL_STRING) {
				//do nothing
			}
			else {
				WordList newAnalyser;
				newAnalyser.setMessage (info.modifyInfo[1]);
				newAnalyser.solveAll();
				bool duration = newAnalyser.isDateStringDuration()||
					newAnalyser.isTimeStringDuration();
				Time timer1,timer2;
				newAnalyser.getStartEndTimer (timer1, timer2);
				(*_eventList)[storageID].setDurationType (duration);
				(*_eventList)[storageID].setStartTime (timer1);
				if (duration) {
					(*_eventList)[storageID].setEndTime (timer2);
				}
			}
			if (info.modifyInfo[2] == NULL_STRING) {
				//do nothing
			}
			else {
				WordList newAnalyser;
				newAnalyser.setMessage (info.modifyInfo[2]);
				newAnalyser.solveAll();
				Time timer1, timer2;
				newAnalyser.getStartEndTimer (timer1, timer2);
				(*_eventList)[storageID].setRemindTime (timer1);
			}

			if (info.modifyInfo[3] == NULL_STRING) {
				//do nothing
			}
			else {
				WordList newAnalyser;
				newAnalyser.setMessage (info.modifyInfo[3]);
				newAnalyser.solveAll();
				(*_eventList)[storageID].setRepeatRule (newAnalyser.getRepeatRule());	
			}

			if (info.modifyInfo[4] == NULL_STRING) {
				//do nothing
			}
			else {
				vector<pos_int> indicesList;
				vector<string> * wordListPtr;
				wordListPtr = (*_eventList)[storageID].getTagPtr(); 
				wordListPtr->clear();
				splitString (info.modifyInfo[4], TAG_SEP_CHAR, TAG_SEP_CHAR_N, *wordListPtr, indicesList);
			}
			undoPtr->push((*_eventList)[storageID], OperationType::MODIFY);
		}
	}
	OutputDisplay::get().showModifySuccess (modifiedIDs, notModifiedIDs);
}

//@author A0083782W
void Operations::markEvent(const MARK_STRUCT& markInfo) {
    vector<pos_int> markedIDs;
	vector<pos_int> notMarkedIDs;
	pos_int nMarked = 0;
	for (auto i:markInfo.IDs) {
		int storageID = _availableData[(pos_int)_currentTab].getStorageIndex (i);
		if (storageID >= 0) {
			ActionList* undoPtr = &ActionList::get();
			markedIDs.push_back (i);
			undoPtr->push((*_eventList)[storageID], OperationType::MODIFY);
			for (auto j:markInfo.filters) {
				switch (j) {
				case MarkFilter::DONE:
					(*_eventList)[storageID].setDone (true);
					break;
				case MarkFilter::NOT_DONE:
					(*_eventList)[storageID].setDone (false);
					break;
				case MarkFilter::MISSED:
					(*_eventList)[storageID].setMissed (true);
					break;
				case MarkFilter::NOT_MISSED:
					(*_eventList)[storageID].setMissed (false);
					break;
				case MarkFilter::REMINDED:
					(*_eventList)[storageID].setReminded (true);
					break;
				case MarkFilter::NOT_REMINDED:
					(*_eventList)[storageID].setReminded (false);
					break;
				case MarkFilter::FAVOURITE:
					(*_eventList)[storageID].setFavourite (true);
					break;
				case MarkFilter::NOT_FAVOURITE:
					(*_eventList)[storageID].setFavourite (false);
					break;
				default:
					break;
				}
			}
			undoPtr->push((*_eventList)[storageID], OperationType::MODIFY);
			nMarked++;
		}
		else {
			notMarkedIDs.push_back(i);
		}
	}
	if (nMarked == 0) {
		markedIDs.clear();
		markedIDs.shrink_to_fit();
		notMarkedIDs.clear();
		notMarkedIDs.shrink_to_fit();
	}
	OutputDisplay::get().showMarked(markedIDs, notMarkedIDs);
}

//@author A0083782W
void Operations::markEvent (pos_int storageID, MarkFilter newFilter) {
    vector<pos_int> markedIDs;
	if (storageID >= 0) {
		ActionList* undoPtr = &ActionList::get();
		switch (newFilter) {
		case MarkFilter::DONE:
			if ((*_eventList)[storageID].getRepeatRule().frequency == RepeatFrequency::NO_REPEAT) {
				(*_eventList)[storageID].setDone (true);
			}
			break;
		case MarkFilter::NOT_DONE:
			(*_eventList)[storageID].setDone (false);
			break;
		case MarkFilter::MISSED:
			(*_eventList)[storageID].setMissed (true);
			break;
		case MarkFilter::NOT_MISSED:
			(*_eventList)[storageID].setMissed (false);
			break;
		case MarkFilter::REMINDED:
			if ((*_eventList)[storageID].getRepeatRule().frequency != RepeatFrequency::NO_REPEAT &&
				(*_eventList)[storageID].getRemindTime().isEmpty() == false) {
				Time emptyTime;
				(*_eventList)[storageID].setRemindTime (emptyTime);
			}
			else {
				(*_eventList)[storageID].setReminded (true);
			}
			break;
		case MarkFilter::NOT_REMINDED:
			(*_eventList)[storageID].setReminded (false);
			break;
		case MarkFilter::FAVOURITE:
			(*_eventList)[storageID].setFavourite (true);
			break;
		case MarkFilter::NOT_FAVOURITE:
			(*_eventList)[storageID].setFavourite (false);
			break;
		default:
			break;
		}
	}
}

//@author A0101056Y
void Operations::executeUndo(UserCommand& aCommand){
	OperationType lastOperation = aCommand.getOperationType();
	switch (lastOperation) {
	case OperationType::DELETE_BY_EVENT:
		deleteByEvent(aCommand.getEvent());
		break;

	case OperationType::ADD:
		addEvent(aCommand.getEvent());
		break;

	default:
		break;
	}
}

//@author A0101056Y
void Operations::performSearch (const SEARCH_STRUCT& searchInfo){
	vector<pos_int> availabelID;

	for (auto i:searchInfo.filters) {
		if (i == SearchFilter::ANYTHING){
			for (pos_int j = 0; j < _eventList->size(); j++) {
				availabelID.push_back (j);
			}
			sortEvents(availabelID);
			break;
		}

		else{
			SearchEngine search; 
			for (pos_int j = 0; j< _eventList->size(); j++){
				search.setEverything((*_eventList)[j], searchInfo, j);
				search.searchEvents();
			}	
			
			vector<SEARCH_RELEVANCE> temp = search.getFoundEvents(); 
			for (pos_int i=0; i<  temp.size() ; i++){
				availabelID.push_back(temp[i].storageIndex);
			}
		}	
	}
	_availableData[0].setMapping (availabelID);
	ReadOutputDisplay ptr = &OutputDisplay::get();
	ptr->displayEvents (availabelID);
}


//@author A0101056Y
void Operations::sortEvents(vector<pos_int>& IDs){
	vector<SORT_STRUCT> sortList;
	vector<SORT_STRUCT> tempList;
	vector<con_str> descriptionList;
	ReadSettingPtr settingPtr = &Settings::get();
	
	for (pos_int i = 0; i < IDs.size(); i++){
		if ((*_eventList)[IDs[i]].getStartTime().isEmpty() == false){
			SORT_STRUCT temp;
			temp.storageIndex = IDs[i];
			temp.startTime = (*_eventList)[IDs[i]].getStartTime();
			temp.description = (*_eventList)[IDs[i]].getDetails();
			sortList.push_back(temp);
	
		}
		else{ 
			SORT_STRUCT temp;
			temp.storageIndex = IDs[i];
			temp.description = (*_eventList)[IDs[i]].getDetails();
			tempList.push_back(temp);
		}
	}
	sort(sortList.begin(), sortList.end(), compareByDescription);
	sort(tempList.begin(), tempList.end(), compareByDescription);

	if (settingPtr->getTimeListingOrder() == DisplayOrder::HIGHT_TO_LOW){
		sort(sortList.begin(), sortList.end(), sortBySmallerTime);
	}
	else if (settingPtr->getTimeListingOrder() == DisplayOrder::LOW_TO_HIGH){
		sort(sortList.begin(), sortList.end(), sortByLargerTime);
	}
	
	IDs.clear();
		
	for (pos_int i = 0; i < sortList.size(); i++){
		IDs.push_back(sortList[i].storageIndex);
	}

	for (pos_int i = 0; i< tempList.size(); i++){
		IDs.push_back(tempList[i].storageIndex);
		
	}
}

//@author A0101056Y
void Operations::saveToFile(){
	EventStorage* ptr = &EventStorage::get();
	ptr->saveFile();
}

//@author A0101056Y
void Operations::loadFromFile(){
	EventStorage* ptr = &EventStorage::get();
	ptr->readFile();
}


